package com.pubinfo.passbook.engine.factory.impl;

import com.pubinfo.passbook.common.entity.TEnginePlaceholderConst;
import com.pubinfo.passbook.common.entity.TEnginePlaceholderEquation;
import com.pubinfo.passbook.common.entity.TEnginePlaceholderVar;
import com.pubinfo.passbook.common.exception.ServiceException;
import com.pubinfo.passbook.common.model.http.engine.ComputeParam;
import com.pubinfo.passbook.common.service.engine.EngineComputePlaceHolderConstService;
import com.pubinfo.passbook.common.service.engine.EngineComputePlaceHolderEquationService;
import com.pubinfo.passbook.common.service.engine.EngineComputePlaceHolderVarService;
import com.pubinfo.passbook.engine.constants.ComputeChainType;
import com.pubinfo.passbook.engine.constants.ComputeVariableSymbol;
import com.pubinfo.passbook.engine.factory.ParamenterProcessorFactory;
import com.pubinfo.passbook.engine.process.ParamenterProcessor;
import com.pubinfo.passbook.engine.constants.ComputeParamType;
import com.pubinfo.passbook.engine.process.constant.ConstantProcessor;
import com.pubinfo.passbook.engine.process.equation.EquationProcessor;
import com.pubinfo.passbook.engine.process.temporary.TemporaryProcessor;
import com.pubinfo.passbook.engine.process.variable.VariableProcessor;
import com.pubinfo.passbook.engine.process.variable.medicine.*;
import com.pubinfo.passbook.engine.process.variable.medicine.payinformation.*;
import com.pubinfo.passbook.engine.utils.ValidateUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.stream.Collectors;

/**
 * 默认的参数处理器工厂
 *
 * @author zhouch
 * @date 2021/12/16 10:40
 */
@Slf4j
@Component
public class DefaultParamenterProcessorFactory implements ParamenterProcessorFactory {

    @Resource
    EngineComputePlaceHolderVarService varService;

    @Resource
    EngineComputePlaceHolderEquationService equationService;

    @Resource
    EngineComputePlaceHolderConstService constService;

    /**
     * 获得参数处理器
     *
     * @param param
     * @param targetChainType
     * @return
     */
    @Override
    public ParamenterProcessor<?, ?> getProcessor(@NonNull ComputeParam param, @NonNull String targetChainType) {
        ParamenterProcessor<?, ?> processor;

        log.info("处理参数处理器信息 => 占位符: {}, 参数类型: {}, 参数处理器返回值类型: {}", param.getName(), param.getType(), param.getResultType());

        switch (param.getType()) {
            case ComputeParamType.VARIABLE:
                /**
                 * 变量占位符
                 */
                processor = getVariableProcessor(param.getParamId());
                break;
            case ComputeParamType.EQUATION:
                /**
                 * 公式占位符
                 */
                processor = getEquationProcessor(param.getParamId());
                break;
            case ComputeParamType.CONSTANT:
                /**
                 * 常量占位符
                 */
                processor = getConstantProcess(param.getParamId());
                break;
            case ComputeParamType.TEMPORARY:
                /**
                 * 临时值占位符
                 */
                processor = getTemporaryProcess(param.getValue());
                break;
            default:
                throw new ServiceException("未知的参数类型: " + param.getType());
        }

        if (processor == null) {
            throw new ServiceException("参数处理器初始化错误: " + param);
        }

        if (false == ValidateUtils.checkProcessorChainType(processor, targetChainType)) {
            throw new ServiceException(
                    "参数处理器计算链类型不匹配: 处理器计算链类型：" +
                            processor.getChainType() +
                            " 目标计算链类型：" +
                            targetChainType);
        }

        // 设置处理器对应的占位符
        processor.setPlaceHolderName(param.getName());

        return processor;
    }


    /**
     * 常量参数处理器，检查常量是否存在，之后把常量ID交给处理器，计算链运行时获得值
     *
     * @param paramId
     * @return
     */
    private ConstantProcessor getConstantProcess(Integer paramId) {
        TEnginePlaceholderConst constInfo = constService.getById(paramId);
        if (constInfo == null) {
            throw new ServiceException("常量占位符不存在 ID：" + paramId);
        }
        log.info("常量处理器: 名称: {}, 值: {}", constInfo.getName(), constInfo.getValue());
        return new ConstantProcessor(paramId);
    }

    /**
     * 公式参数处理器
     * <p>
     * 公式处理器比较特别，公式内部可能存在变量占位符和常量占位符
     *
     * @param paramId
     * @return
     */
    private EquationProcessor<?> getEquationProcessor(Integer paramId) {
        TEnginePlaceholderEquation equationInfo = equationService.getById(paramId);

        if (equationInfo == null) {
            throw new ServiceException("公式占位符不存在 ID: " + paramId);
        }

        log.info("处理公式信息 => 名称: {}, 公式体: {}, 描述: {}", equationInfo.getName(), equationInfo.getBody(),
                equationInfo.getDescription());

        EquationProcessor<?> equationProcessor = new EquationProcessor<>(equationInfo);

        /**
         * 公式中的参数处理器初始化
         */
        Collection<? extends ParamenterProcessor<?, ?>> processors = equationInfo
                .getParamList()
                .stream()
                .map(
                        t -> getProcessor(t, equationInfo.getChainType()
                        )
                )
                .collect(Collectors.toList());

        equationProcessor.setProcessors(processors);

        return equationProcessor;
    }

    /**
     * 变量参数处理器, 通过变量标识符初始化
     *
     * @param paramId
     * @return
     */
    private VariableProcessor<?> getVariableProcessor(Integer paramId) {
        TEnginePlaceholderVar varInfo = varService.getById(paramId);

        if (varInfo == null) {
            throw new ServiceException("变量占位符不存在 ID:" + paramId);
        }

        log.info("变量处理器信息: 名称: {}, 描述: {}", varInfo.getName(), varInfo.getDescription());

        switch (varInfo.getChainType()) {
            case ComputeChainType.MEDICINE:

                // 医药费变量
                switch (varInfo.getSymbol().trim()) {
                    case ComputeVariableSymbol.Medicine.ACCOUNT_PAY_AMOUNT:
                        return new AccountPayAmountProcessor();
                    case ComputeVariableSymbol.Medicine.AMOUNT_SELF:
                        return new AmountSelfProcessor();
                    case ComputeVariableSymbol.Medicine.BURDEN_SELF_AMOUNT:
                        return new BurdenSelfAmountProcessor();
                    case ComputeVariableSymbol.Medicine.BING_AMOUNT_SELF:
                        return new BingAmountSelfProcessor();
                    case ComputeVariableSymbol.Medicine.ALL_AMOUNT_SELF:
                        return new AllAmountSelfProcessor();
                    case ComputeVariableSymbol.Medicine.FUND_PAY_AMOUNT:
                        return new FundPayAmountProcessor();
                    case ComputeVariableSymbol.Medicine.SELF_PAYMENT_AMOUNTS:
                        return new SelfpaymentAmountsProcessor();
                    case ComputeVariableSymbol.Medicine.TOTAL_AMOUNTS:
                        return new TotalAmountsProcessor();
                    case ComputeVariableSymbol.Medicine.TOTAL_AVAILABILITY_PAYS:
                        return new TotalAvailabilityPaysProcessor();
                    case ComputeVariableSymbol.Medicine.HISTORY_HOSPITAL_SUM:
                        return new HistoryHospitalSumProcessor();
                    case ComputeVariableSymbol.Medicine.HISTORY_NORMAL_SUM:
                        return new HistoryNormalSumProcessor();
                    case ComputeVariableSymbol.Medicine.TAI_SHUN_START_PAYMENT:
                        return new TaiShunStartPaymentProcessor();
                    case ComputeVariableSymbol.Medicine.TAI_SHUN_MAXIMUM_AMOUNT:
                        return new TaiShunMaximumAmountProcessor();
                    case ComputeVariableSymbol.Medicine.COST_SUM_AMOUNT:
                        return new CostSumAmountProcessor();
                    case ComputeVariableSymbol.Medicine.OWN_PAY_AMOUNT:
                        return new OwnPayAmountProcessor();
                    case ComputeVariableSymbol.Medicine.CARE_SUM_AMOUNT:
                        return new CareSumAmountProcessor();
                    case ComputeVariableSymbol.Medicine.INDIVIDUAL_ACCT_PREV_PAY:
                        return new IndividualAcctPrevPayProcessor();
                    case ComputeVariableSymbol.Medicine.WORK_AGE:
                        return new WorkAgeProcessor();
                    default:
                        throw new ServiceException("未知的医药费变量处理器: " + varInfo.getSymbol() + " = " + varInfo.getName());
                }

            case ComputeChainType.MEDICINE_SUB_LOOP:

                // 医药费子单
                switch (varInfo.getSymbol().trim()) {
                    case ComputeVariableSymbol.PayInformation.CARE:
                        return new CareProcessor();
                    case ComputeVariableSymbol.PayInformation.BY_COST_NAME:
                        return new ByCostNameProcessor();
                    case ComputeVariableSymbol.PayInformation.SHENG_GONG_SI_BY_COST_NAME:
                        return new SGSByCostNameProcessor();
                    case ComputeVariableSymbol.PayInformation.COST:
                        return new CostProcessor();
                    case ComputeVariableSymbol.PayInformation.OWN_PAY:
                        return new OwnPayProcessor();
                    case ComputeVariableSymbol.PayInformation.CURRENT_YEAR_PAY:
                        return new CurrentYearPayProcessor();
                    case ComputeVariableSymbol.PayInformation.TOTAL:
                        return new TotalProcessor();
                    case ComputeVariableSymbol.PayInformation.FUND_PAY:
                        return new FundPayProcessor();
                    default:
                        throw new ServiceException("未知的医药费子单变量处理器: " + varInfo.getSymbol() + " = " + varInfo.getName());
                }

            default:
                throw new ServiceException("不支持的变量处理器计算链类型: " + varInfo.getChainType());
        }
    }


    /**
     * 临时变量直接赋值就行，不需要做额外处理
     *
     * @param value
     * @return
     */
    private TemporaryProcessor getTemporaryProcess(BigDecimal value) {
        log.info("临时值处理器: 值: {}", value);
        return new TemporaryProcessor(value);
    }

}
